package com.unionpay.upyzt;

import com.unionpay.upyzt.exception.InvalidRequestException;
import com.unionpay.upyzt.net.Logger;
import com.unionpay.upyzt.util.Sm2Utils;
import com.unionpay.upyzt.util.StreamUtils;
import com.unionpay.upyzt.util.StringUtils;
import lombok.Getter;
import lombok.Setter;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.InvalidKeyException;

/**
 * Upyzt Base class
 */
public abstract class Upyzt {

    /**
     * SDK version
     */
    public static final String VERSION = "1.3.6";

    /**
     * 生产环境 API BASE URL
     */
    public static final String LIVE_API_BASE = "https://yunzhitong.95516.com/api";

    /**
     * 测试环境 API BASE URL
     * 注：要修改为测试环境，需使用 Upyzt.setApiBase(Upyzt.TEST_API_BASE);
     */
    public static final String TEST_API_BASE = "https://unionpay-api.pinpula.com/api";

    /**
     * 默认网络连接超时时间，单位：毫秒
     */
    public static final int DEFAULT_CONNECT_TIMEOUT = 30 * 1000;

    /**
     * 默认数据读取超时时间，单位：毫秒
     */
    public static final int DEFAULT_READ_TIMEOUT = 80 * 1000;

    /**
     * 默认 Accept Language
     */
    public static final String DEFAULT_ACCEPT_LANGUAGE = "zh-CN";

    /**
     * 是否开启调试模式
     * <p>
     * 开启调试会在控制台额外打印调试信息
     */
    public static boolean DEBUG = true;

    /**
     * API BASE URL
     */
    @Getter
    @Setter
    private static volatile String apiBase = LIVE_API_BASE;

    /**
     * 平台商户 Id
     * <p>
     * 请求 API 时设置于在 Request Header 中，用于表明请求的身份
     * <p>
     * 获取方式：管理后台 -> 开发管理 -> 开发信息管理 -> 平台编号
     */
    @Getter
    @Setter
    private static volatile String rootMchId;

    /**
     * 请求签名*私钥*
     * <p>
     * 对请求 API 进行数字签名，来确定请求的完整性和真实性
     * 使用 SHA256withRSA 作为数字签名的算法
     * <p>
     * 获取方式：请自行生成 RSA 密钥对（base64）。
     * 将*私钥*请填写于此处，并妥善保管，若私钥丢失或泄露，请及时更换；
     * 并将公钥填写到管理后台 -> 开发管理 -> 开发信息管理 -> 请求签名公钥
     */
    @Getter
    @Setter
    private static volatile String signPrivateKey;

    /**
     * 敏感字段加密公钥
     * <p>
     * 针对敏感数据比如交易验证码等，需要对相应字段进行加密后传输
     * <p>
     * 获取方式：从管理后台 -> 开发管理 -> 开发信息管理 -> 敏感字段加密公钥
     */
    @Getter
    @Setter
    private static volatile String encryptPublicKey;

    /**
     * 响应验证公钥
     * <p>
     * 用于对 API 返回参数进行验证，以确保信息没有被第三方篡改
     * <p>
     * 获取方式：从管理后台 -> 开发管理 -> 开发信息管理 -> 响应验证公钥
     */
    @Getter
    @Setter
    private static volatile String verifyPublicKey;

    /**
     * Accept Language
     */
    @Getter
    @Setter
    private static volatile String acceptLanguage = DEFAULT_ACCEPT_LANGUAGE;

    /**
     * 网络连接超时时间，单位：毫秒
     */
    @Getter
    @Setter
    private static volatile int connectTimeout = DEFAULT_CONNECT_TIMEOUT;

    /**
     * 数据读取超时时间，单位：毫秒
     * <p>
     * 不同接口的耗时时间不一样，部分接口的耗时可能比较长。
     */
    @Getter
    @Setter
    private static volatile int readTimeout = DEFAULT_READ_TIMEOUT;

    /**
     * 连接失败时的最大重试次数
     */
    @Getter
    @Setter
    private static volatile int maxNetworkRetries = 1;

    /**
     * 可自定义 logger 记录 http 请求日志
     */
    @Getter
    @Setter
    private static Logger logger;

    /**
     * 敏感字段加密
     *
     * @param value 待加密的值
     * @return 加密后的值
     * @throws InvalidRequestException 无效的 Encrypt Public Key
     */
    public static String encryptField(String value) throws InvalidRequestException {
        if (StringUtils.isBlank(Upyzt.getEncryptPublicKey())) {
            throw new InvalidRequestException("未设置正确的 Upyzt.encryptPublicKey，无法进行敏感字段加密");
        }
        return encryptField(value, Upyzt.getEncryptPublicKey());
    }

    /**
     * 敏感字段加密
     *
     * @param value 待加密的值
     * @param encryptPublicKey 加密公钥
     * @return 加密后的值
     * @throws InvalidRequestException 无效的 Encrypt Public Key
     */
    public static String encryptField(String value, String encryptPublicKey) throws InvalidRequestException {
        if (StringUtils.isBlank(encryptPublicKey)) {
            throw new InvalidRequestException("未设置正确的 encryptPublicKey，无法进行敏感字段加密");
        }
        try {
            return Sm2Utils.encrypt(value, encryptPublicKey);
        } catch (InvalidKeyException e) {
            if (Upyzt.DEBUG) {
                e.printStackTrace();
            }
            throw new InvalidRequestException("敏感字段加密失败", e);
        }
    }

    /**
     * 从文件读取请求签名私钥并设置
     *
     * @param keyFilePath 密钥文件路径
     * @throws IOException IO 异常
     */
    public static void setSignPrivateKeyPath(String keyFilePath) throws IOException {
        Upyzt.signPrivateKey = readKeyFromFilePath(keyFilePath);
    }

    /**
     * 从文件读取敏感字段加密公钥并设置
     *
     * @param keyFilePath 密钥文件路径
     * @throws IOException IO 异常
     */
    public static void setEncryptPublicKeyPath(String keyFilePath) throws IOException {
        Upyzt.encryptPublicKey = readKeyFromFilePath(keyFilePath);
    }

    /**
     * 从文件读取响应验证公钥并设置
     *
     * @param keyFilePath 密钥文件路径
     * @throws IOException IO 异常
     */
    public static void setVerifyPublicKeyPath(String keyFilePath) throws IOException {
        Upyzt.verifyPublicKey = readKeyFromFilePath(keyFilePath);
    }

    /**
     * 读取密钥文件
     *
     * @param filePath 文件路径
     * @return 密钥内容
     * @throws IOException IO 异常
     */
    private static String readKeyFromFilePath(String filePath) throws IOException {
        FileInputStream inputStream = new FileInputStream(filePath);
        String privateKey = StreamUtils.readToEnd(inputStream, com.unionpay.upyzt.net.APIResource.CHARSET);
        inputStream.close();

        return privateKey;
    }


}
